/**
 * GhostDictionary maintains a list of words for playing Ghost.
 * <br>
 * This source code is copyright 2008 by Patrick May.  All rights
 * reserved.
 *
 * @author Patrick May (patrick.may@mac.com)
 * @author &copy; 2008 Patrick May.  All rights reserved.
 * @version 1
 */

package com.game.ghost.webapp.apps.service.impl;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ListIterator;
import java.util.NoSuchElementException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

import com.game.ghost.webapp.apps.data.WordDictionary;
import com.game.ghost.webapp.apps.data.WordNode;
import com.game.ghost.webapp.apps.service.CacheManager;
import com.game.ghost.webapp.apps.service.GhostWordsService;

/**
 * Service class for providing word calculation service to player and game executor
 * 
 * @author gautak
 *
 */
@Service
public class GhostWordsServiceImpl implements GhostWordsService,
		InitializingBean {
	private static Log logger_ = LogFactory.getLog(GhostWordsServiceImpl.class);

	private static final String FULL_DICTIONARY = "";

	@Autowired
	ApplicationContext appContext;

	@Value("${word.min.length}")
	private Integer MIN_WORD_LENGTH;

	@Value("${word.file.path}")
	private String wordListFilePath;

	@Autowired
	@Qualifier(value = "localCache")
	private CacheManager cacheManager;

	public GhostWordsServiceImpl() {

	}

	/**
	 * Add a word to the dictionary if it meets two criteria:
	 * <ul>
	 * <li>The word must be at least MIN_WORD_LENGTH letters long.
	 * <li>The word must not start with another valid word.
	 * </ul>
	 * Words are assumed to be added in alphabetical order, so the second
	 * criteria can be evaluated simply by comparing the word with the last word
	 * in the list.
	 * 
	 * @param word
	 *            The word to add to the dictionary.
	 */
	private void addWord(String word, WordDictionary wordDictionary) {
		String lastWord = null;

		if (word.length() >= MIN_WORD_LENGTH) {
			try {
				lastWord = wordDictionary.getWordList().getLast();
			} catch (NoSuchElementException e) {
			}

			if ((lastWord == null) || !word.startsWith(lastWord))
				wordDictionary.getWordList().addLast(word);
		}
	}

	/**
	 * The file constructor for the GhostDictionary class.
	 * 
	 * @param fileName
	 *            The name of the dictionary file. This file is assumed to be
	 *            sorted in alphabetical order.
	 */
	@Override
	public void afterPropertiesSet() throws Exception {		
		setupDefaults();		
	}
	
	/**
	 * Setup default WordDictionary and Word node tree with complete word list from the file provided
	 * 
	 * @throws IOException
	 */
	private void setupDefaults() throws IOException{
		Resource wordListResource = null;
		BufferedReader reader = null;
		try {

			wordListResource = appContext.getResource(wordListFilePath);

			reader = new BufferedReader(new InputStreamReader(
					wordListResource.getInputStream()));

			String word = null;
			WordDictionary wordDictionary = createWordDictionaryObject(FULL_DICTIONARY);

			while ((word = reader.readLine()) != null) {
				addWord(word, wordDictionary);
			}

			updateCachingMaps(FULL_DICTIONARY, wordDictionary);

		} catch (FileNotFoundException fileNotFoundException) {
			//logger_.fatal("File " + wordListResource.getFilename() + " not found.");
		} catch (IOException ioException) {
			//logger_.fatal("Error reading file:  " + ioException.toString());
		} finally{
			if(null != reader){
				reader.close();
				reader = null;
			}
		}
	}

	/**
	 * The dictionary subset constructor for the GhostDictionary class. This
	 * constructor builds a dictionary from all of the words in the specified
	 * dictionary that begin with the specified string.
	 * 
	 * @param dictionary
	 *            The base dictionary.
	 * @param start
	 *            The starting string.
	 */
	public WordDictionary getWordDictionary(String wordInPlay,
			String enteredLetter) {

		WordDictionary subsetWordDictionary = getSubsetWordDictionary(
				fetchClosestMatchWordDictionary(wordInPlay), wordInPlay,
				enteredLetter);

		return subsetWordDictionary;

	}

	private WordDictionary getSubsetWordDictionary(
			WordDictionary wordDictionary, String wordInPlay, String filter) {

		if (filter.isEmpty()) {
			return wordDictionary;
		}

		String startFilter = wordInPlay + filter;

		WordDictionary subsetWordDictionary = null;

		subsetWordDictionary = cacheManager.get(WordDictionary.CACHE_NAME, startFilter, subsetWordDictionary);

		if (null != subsetWordDictionary) {

			return subsetWordDictionary;

		} else {

			subsetWordDictionary = createWordDictionaryObject(startFilter);

			String nextWord = null;
			ListIterator<String> iterator = wordDictionary.getWordList()
					.listIterator(0);

			while (iterator.hasNext()) {
				if ((nextWord = iterator.next()).startsWith(startFilter)) {
					subsetWordDictionary.getWordList().addLast(nextWord);
				}
			}

			updateCachingMaps(startFilter, subsetWordDictionary);
		}

		return subsetWordDictionary;
	}

	public WordNode getWordNode(String start) {
		
		//WordDictionary wordDictionary = getWordDictionary(start, "");
		
		WordNode wordNode = null;
		
		wordNode = cacheManager.get(WordNode.CACHE_NAME, start, wordNode);

		return wordNode.childNode(start);
	}

	/**
	 * creates new WordDictionary object using prototype bean defined in Spring Context.
	 * 
	 * @param startFilter
	 * @return
	 */
	private WordDictionary createWordDictionaryObject(String startFilter) {

		WordDictionary wordDictionary = (WordDictionary) appContext.getBean("wordDictionary");

		wordDictionary.setWord(startFilter);

		return wordDictionary;
	}

	public WordDictionary fetchClosestMatchWordDictionary(String start) {

		if (start.isEmpty()) {
			
			WordDictionary wordDictionary = null;

			wordDictionary = cacheManager.get(WordDictionary.CACHE_NAME, FULL_DICTIONARY, wordDictionary);
			
			return wordDictionary;
		}

		WordDictionary wordDictionary = null;

		wordDictionary = cacheManager.get(WordDictionary.CACHE_NAME, start,
				wordDictionary);

		if (null == wordDictionary) {
			return fetchClosestMatchWordDictionary(start.substring(0,
					start.length() - 1));
		}

		return wordDictionary;
	}

	/**
	 * Method to update wordDictionary into cache and also to create WordNode tree at the same time and make
	 * it available to the cache for further use
	 * 
	 * @param start
	 * @param wordDictionary
	 */
	private void updateCachingMaps(String start, WordDictionary wordDictionary) {
		
		cacheManager.add(WordDictionary.CACHE_NAME, start, wordDictionary);
		
		WordNode wordNode = new WordNode(start,	wordDictionary.getWordList().listIterator());
			
		cacheManager.add(WordNode.CACHE_NAME, start, wordNode);
		
	}
	
	/**
	 * Method to clear cache. It takes care to add full dictionary and word node tree as default 
	 * to be available 
	 * 
	 */
	public void clearCache(){

		WordDictionary wordDictionary = null;
		WordNode wordNode = null;
		
		wordDictionary = cacheManager.get(WordDictionary.CACHE_NAME, FULL_DICTIONARY, wordDictionary);		
		wordNode = cacheManager.get(WordNode.CACHE_NAME, FULL_DICTIONARY, wordNode);
		
		cacheManager.clearCache();
		
		cacheManager.add(WordDictionary.CACHE_NAME, FULL_DICTIONARY, wordDictionary);
		cacheManager.add(WordNode.CACHE_NAME, FULL_DICTIONARY, wordNode);
	}

}
